package recallin.navigation;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;


import org.mapsforge.core.GeoPoint;
import org.recallin.core.model.Tag;
import org.recallin.core.model.Tile;
import org.recallin.core.util.MercatorProjection;
import org.recallin.map.reader.MapDatabase;
import org.recallin.map.reader.MapReadResult;
import org.recallin.map.reader.Way;
import org.recallin.map.reader.header.FileOpenResult;

import android.os.Environment;

/**
 * Reads all the data from the .map file and index them for the further searching
 * this mode is for footway so include all the path possible
 * @author Khashayar
 *
 */
public class ReaderFootMode {
	private static final File MAP_FILE = new File(
			Environment
			.getExternalStorageDirectory().getPath()
			+ "/Recallin/maps/sweden.map");
	private static final byte ZOOM_LEVEL = 16;

	private PathWay runTest(File mapFile , GeoPoint startPoint , GeoPoint endPoint) {

		MapDatabase mapDatabase = new MapDatabase();
		FileOpenResult fileOpenResult = mapDatabase.openFile(mapFile);
		
		HashMap<Long, recallin.navigation.Way> ways = new HashMap<Long, recallin.navigation.Way>();
		HashMap<Long, Node> nodes = new HashMap<Long, Node>();
		HashMap<Node, Long> nodeRefrences = new HashMap<Node, Long>();

		Long wayIdCounter = 1000000l;
		Long nodeIdCounter = 1l;
		
		
		
		double minDistanceToEnd = Double.MAX_VALUE;
		double minDistanceToStart = Double.MAX_VALUE;
		Long startId = 0l;
		Long endId = 0l;
		
		long startTileX = MercatorProjection.longitudeToTileX(startPoint.getLongitude(), ZOOM_LEVEL);
		long startTileY = MercatorProjection.latitudeToTileY(startPoint.getLatitude(), ZOOM_LEVEL);
		long endTileX = MercatorProjection.longitudeToTileX(endPoint.getLongitude(), ZOOM_LEVEL);
		long endTileY = MercatorProjection.latitudeToTileY(endPoint.getLatitude(), ZOOM_LEVEL);


		if(startTileX == endTileX && startTileY == endTileY){
			Tile tile = new Tile(startTileX, startTileY, ZOOM_LEVEL);
			MapReadResult mapReadResult = mapDatabase.readMapData(tile);
			
			for (Way way : mapReadResult.ways) {
				recallin.navigation.Way w  = new recallin.navigation.Way();
				HashMap<String, String> tags = new HashMap<String, String>();
				for (Tag tag : way.tags) {
					tags.put(tag.key, tag.value);
				}
				tags.remove("oneway");
				if(tags.containsKey("highway") ){
					for (int i = 0; i < way.wayNodes.length; i++) {
						for (int j = 0; j < way.wayNodes[i].length-1; j+=2) {
							Node node = new Node(Double.valueOf(way.wayNodes[i][j+1] / 1000000), Double.valueOf(way.wayNodes[i][j] / 1000000));

							double tempDistanceStart = calculateDistance(node, startPoint);
							double tempDistanceEnd = calculateDistance(node, endPoint);
							
							
							if(!nodes.containsValue(node)){
								nodes.put(nodeIdCounter, node);
								nodeRefrences.put(node, nodeIdCounter);
								w.getNodes().add(nodeIdCounter);
								if(tempDistanceStart <  minDistanceToStart){
									minDistanceToStart = tempDistanceStart;
									startId = nodeIdCounter;
								}
								if(tempDistanceEnd <  minDistanceToEnd){
									minDistanceToEnd = tempDistanceEnd;
									endId = nodeIdCounter;
								}
								nodeIdCounter++;
							}else{
								if(tempDistanceStart <  minDistanceToStart){
									minDistanceToStart = tempDistanceStart;
									startId = nodeRefrences.get(node);
								}
								if(tempDistanceEnd <  minDistanceToEnd){
									minDistanceToEnd = tempDistanceEnd;
									endId = nodeRefrences.get(node);
								}
//								System.out.println("HERE");
								w.getNodes().add(nodeRefrences.get(node));
							}
						}
					}
					if(w.getNodes().get(0).equals(w.getNodes().get(w.getNodes().size()-1))){
						tags.put("oneway", "yes");
						tags.put("round", "yes");
						
						ArrayList<Integer> areas = new ArrayList<Integer>();
						Double midX = nodes.get(w.getNodes().get(0)).getLongitude() * 1000;
						Double midY = nodes.get(w.getNodes().get(0)).getLatitude() * 1000;
						areas.add(0);
						for (int i = 1; i < w.getNodes().size() - 1; i++) {

							double difX = nodes.get(w.getNodes().get(i)).getLongitude() * 1000 - midX;
							double difY = nodes.get(w.getNodes().get(i)).getLatitude() * 1000 - midY;

							if (difX > 0 && difY >= 0) {
								if (areas.get(areas.size() - 1) != 1) {
									areas.add(1);
								}
							}
							if (difX > 0 && difY < 0) {
								if (areas.get(areas.size() - 1) != 2) {
									areas.add(2);
								}
							}
							if (difX < 0 && difY >= 0) {
								if (areas.get(areas.size() - 1) != 4) {
									areas.add(4);
								}
							}
							if (difX < 0 && difY < 0) {
								if (areas.get(areas.size() - 1) != 3) {
									areas.add(3);
								}
							}
						}
						areas = clearAreas(areas);
						int x = isClockWise(areas);
						if (x == 1) {
							Collections.reverse(w.getNodes());
						}

					}
					w.setTags(tags);

					if(!ways.containsValue(w)){
						ways.put(wayIdCounter, w);
						wayIdCounter += 1000000l;
					}
				}			
			}		
		}else{
			Tile tileStart = new Tile(startTileX, startTileY, ZOOM_LEVEL);
			MapReadResult mapReadResultStart = mapDatabase.readMapData(tileStart);
			
			for (Way way : mapReadResultStart.ways) {
				recallin.navigation.Way w  = new recallin.navigation.Way();
				HashMap<String, String> tags = new HashMap<String, String>();
				for (Tag tag : way.tags) {
					tags.put(tag.key, tag.value);
				}
				
				if(tags.containsKey("highway")){
					for (int i = 0; i < way.wayNodes.length; i++) {
						for (int j = 0; j < way.wayNodes[i].length-1; j+=2) {
							Node node = new Node(Double.valueOf(way.wayNodes[i][j+1] / 1000000), Double.valueOf(way.wayNodes[i][j] / 1000000));

							double tempDistanceStart = calculateDistance(node, startPoint);
							if(!nodes.containsValue(node)){
								nodes.put(nodeIdCounter, node);
								nodeRefrences.put(node, nodeIdCounter);
								w.getNodes().add(nodeIdCounter);
								if(tempDistanceStart <  minDistanceToStart){
									minDistanceToStart = tempDistanceStart;
									startId = nodeIdCounter;
								}
								nodeIdCounter++;
							}else{
								if(tempDistanceStart <  minDistanceToStart){
									minDistanceToStart = tempDistanceStart;
									startId = nodeRefrences.get(node);
								}
								w.getNodes().add(nodeRefrences.get(node));
							}
						}
					}
					w.setTags(tags);
					ways.put(wayIdCounter, w);
					
					wayIdCounter += 1000000l;
					
				}			
			}		

			//////////////////////////////////////
			
			Tile tileEnd = new Tile(endTileX, endTileY, ZOOM_LEVEL);
			MapReadResult mapReadResultEnd = mapDatabase.readMapData(tileEnd);
			
			for (Way way : mapReadResultEnd.ways) {
				recallin.navigation.Way w  = new recallin.navigation.Way();
				HashMap<String, String> tags = new HashMap<String, String>();
				for (Tag tag : way.tags) {
					tags.put(tag.key, tag.value);
				}
				
				if(tags.containsKey("highway")){
					for (int i = 0; i < way.wayNodes.length; i++) {
						for (int j = 0; j < way.wayNodes[i].length-1; j+=2) {
							Node node = new Node(Double.valueOf(way.wayNodes[i][j+1] / 1000000), Double.valueOf(way.wayNodes[i][j] / 1000000));

							double tempDistanceEnd = calculateDistance(node, endPoint);
							if(!nodes.containsValue(node)){
								nodes.put(nodeIdCounter, node);
								nodeRefrences.put(node, nodeIdCounter);
								w.getNodes().add(nodeIdCounter);
								if(tempDistanceEnd <  minDistanceToEnd){
									minDistanceToEnd = tempDistanceEnd;
									endId = nodeIdCounter;
								}
								nodeIdCounter++;
							}else{
								if(tempDistanceEnd <  minDistanceToEnd){
									minDistanceToEnd = tempDistanceEnd;
									endId = nodeRefrences.get(node);
								}
								w.getNodes().add(nodeRefrences.get(node));
							}
						}
					}
					w.setTags(tags);
					ways.put(wayIdCounter, w);
					
					wayIdCounter += 1000000l;
					
				}			
			}		
		}
		
		
		ArrayList<Long> tiles = getAllBoxes(startTileX, startTileY, endTileX, endTileY);
		
		for (int k = 0; k < tiles.size() - 1 ; k+=2) {
			long tileX = tiles.get(k);
			long tileY = tiles.get(k+1);
			Tile tile = new Tile(tileX, tileY, ZOOM_LEVEL);
			MapReadResult mapReadResult = mapDatabase.readMapData(tile);
			
			for (Way way : mapReadResult.ways) {
				recallin.navigation.Way w  = new recallin.navigation.Way();
				HashMap<String, String> tags = new HashMap<String, String>();
				for (Tag tag : way.tags) {
					tags.put(tag.key, tag.value);
				}
				
				if(tags.containsKey("highway")){
					for (int i = 0; i < way.wayNodes.length; i++) {
						for (int j = 0; j < way.wayNodes[i].length-1; j+=2) {
							Node node = new Node(Double.valueOf(way.wayNodes[i][j+1] / 1000000), Double.valueOf(way.wayNodes[i][j] / 1000000));

							double tempDistanceStart = calculateDistance(node, startPoint);
							double tempDistanceEnd = calculateDistance(node, endPoint);
							
							if(tempDistanceStart <  minDistanceToStart){
								minDistanceToStart = tempDistanceStart;
								startId = nodeIdCounter;
							}
							if(tempDistanceEnd <  minDistanceToEnd){
								minDistanceToEnd = tempDistanceEnd;
								endId = nodeIdCounter;
							}
							
							if(!nodes.containsValue(node)){
								nodes.put(nodeIdCounter, node);
								nodeRefrences.put(node, nodeIdCounter);
								w.getNodes().add(nodeIdCounter);
								nodeIdCounter++;
							}else{
								w.getNodes().add(nodeRefrences.get(node));
							}
						}
					}
					w.setTags(tags);
					ways.put(wayIdCounter, w);
					
					wayIdCounter += 1000000l;
					
				}			
			}		
		}
		
		mapDatabase.closeFile();
		
		
		FindPath findPath = new FindPath(ways , nodes);

		return findPath.getWay(Long.valueOf(startId), Long.valueOf(endId));

		
		
		
	}

	public PathWay executeQueryTest(GeoPoint startPoint , GeoPoint endPoint) {
		return runTest(MAP_FILE , startPoint , endPoint);
	}

	private static double calculateDistance(Node node, GeoPoint point) {
		double distance = Math.sqrt((node.getLongitude()-point.getLongitude())*(node.getLongitude()-point.getLongitude()) 
				+ (node.getLatitude()-point.getLatitude())*(node.getLatitude()-point.getLatitude()));
		return distance * 1000;
	}
	
	private static ArrayList<Long> getAllBoxes(long tileStartX , long tileStartY , long tileEndX , long tileEndY) {
		
		ArrayList<Long> result = new ArrayList<Long>();
		
		long minX;
		long maxX;
		long minY;
		long maxY;
		
		
		if(tileStartX < tileEndX){
			minX = tileStartX;
			maxX = tileEndX;
		}else{
			maxX = tileStartX;
			minX = tileEndX;
		}
		if(tileStartY < tileEndY){
			minY = tileStartY;
			maxY = tileEndY;
		}else{
			maxY = tileStartY;
			minY = tileEndY;
		}
		
		for (long i = minX; i <= maxX; i++) {
			for (long j = minY; j <= maxY; j++) {
				if(i == tileStartX && j == tileStartY){
					//Do Nothing
				}else if(i == tileEndX && j == tileEndY){
					// Do Nothing
				}else{
					result.add(i);
					result.add(j);
				}
			}
		}
		
		return result;
		
	}

	private static ArrayList<Integer> clearAreas(ArrayList<Integer> areas) {

		boolean correct = false;

		while (!correct) {
			correct = true;
			int len = areas.size();
			for (int i = 0; i < len - 3; i++) {
				if (areas.get(i) == areas.get(i + 2)) {
					correct = false;
					areas.remove(i + 2);
					areas.remove(i + 1);
					i = len;
				}
			}
		}

		return areas;
	}

	private static int isClockWise(ArrayList<Integer> areas) {

		boolean clockWise = true;
		boolean clockUnWise = true;
		for (int i = 1; i < areas.size() - 1; i++) {
			if (areas.get(i) > areas.get(i + 1)) {
				if(areas.get(i) == 4 && areas.get(i + 1) == 1){
					// Do Nothing
				}else{
					clockWise = false;
				}
			}
			if (areas.get(i) < areas.get(i + 1)) {
				if(areas.get(i) == 1 && areas.get(i + 1) == 4){
					// Do Nothing
				}else{
					clockUnWise = false;
				}
			}
		}

		if (clockWise)
			return 1;
		if (clockUnWise)
			return 2;
		return -1;
	}

}